#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>

#if defined(WIN32) && defined(UNICODE)
#define USE_UNICODE
#else
#include <fcntl.h>
#endif

#include <pfs.h>
#include <mbwc.h>

int pfs_errno = 0;

Path::Path(const char_type *p)
    : name(NULL), size(0), len(0)
{
    assign(p);
}

Path::Path(const char_type *p, int l)
    : name(NULL), size(0), len(0)
{
    assign(p, l);
}

Path::~Path()
{
    if (name)
        free(name);
}

void Path::to_target_platform()
{
    if (len == 0)
        return;

#ifdef WIN32
    char_type from = '/';
    char_type to   = '\\';
#else
    char_type from = '\\';
    char_type to   = '/';
#endif

#ifdef USE_UNICODE
    char *buf;
    int   n;

    if (get_mbs(&buf, &n)) {
        int i;
        int cnt = 0;
        for (i = 0; i < n; i++) {
            if (buf[i] == from) {
                buf[i] = to;
                ++cnt;
            }
        }

        if (cnt > 0) {
            assign_mbs(buf);
        }

        free (buf);
    }
#else
    int i;

    for (i = 0; i < len; i++) {
        if (name[i] == from)
            name[i] = to;
    }
#endif
}

void Path::assign(const char_type *p)
{
    int l;

#ifdef WIN32
    const char_type *tmp = p;
    while (*tmp)
        ++tmp;
    l = ((char *)tmp - (char *)p) / sizeof (char_type);
#else
    l = strlen(p);
#endif

    assign(p, l);
}

void Path::assign(const char_type *p, int l)
{
    len = l;

    if (size < len + 1) {
        size = len + 1;

        int bytes = size * sizeof (char_type);

        name = (char_type *) realloc (name, bytes);
    }

    memcpy(name, p, len * sizeof (char_type));
    name[len] = 0;
}

bool Path::assign_mbs(const char *p)
{
#ifdef USE_UNICODE
    // translate multiple bytes to wide char
    int      r;
    wchar_t *wide;

    r = mbwc_to_wcs(&wide, p);

    if (r > 0) {
        assign(wide, r);
        free(wide);
    }
    else if (r == 0) {
        len = 0;
        if (size > 0 && name)
            name[0] = 0;
    }

    return r >= 0;
#else
    assign(p);
    return true;
#endif
}

bool Path::assign_wcs(const wchar_t *p)
{
#ifdef USE_UNICODE
    assign(p);
    return true;
#else
    // translate wide char to multiple bytes
    char *buf;
    int   r;

    r = mbwc_to_mbs(&buf, p);

    if (r > 0) {
        assign(buf, r);
        free(buf);
    }
    else if (r == 0) {
        len = 0;
        if (size > 0 && name)
            name[0] = 0;
    }

    return r >= 0;
#endif
}

bool Path::get_mbs(char **buf, int *n) const
{
    if (name == NULL) {
        *buf = NULL;
        *n = 0;
        return true;
    }

#ifdef USE_UNICODE
    int r;

    r = mbwc_to_mbs(buf, name);

    if (r >= 0) {
        *n = r;
        return true;
    }

    return false;
#else
    char *tmp = (char *) malloc (len + 1);
    memcpy(tmp, name, len);
    tmp[len] = 0;

    *buf = tmp;
    *n = len;

    return true;
#endif
}

bool Path::get_wcs(wchar_t **buf, int *n) const
{
    if (name == NULL) {
        *buf = NULL;
        *n = 0;
        return true;
    }

#ifdef USE_UNICODE
    wchar_t *tmp;

    tmp = (wchar_t *) malloc ((len + 1) * 2);

    memset(tmp, name, len * 2);
    tmp[len] = 0;

    *buf = tmp;
    *n = len;

    return true;
#else
    int r;

    r = mbwc_to_wcs(buf, name);

    if (r >= 0) {
        *n = r;
        return true;
    }

    return false;
#endif
}

int
pfs_list_dir(const Path& path, Paths& result)
{
    result.clear();

#ifdef WIN32
    HANDLE          handle;
    WIN32_FIND_DATA fdata;

    if (path.name == NULL)
        return -1;

    handle = FindFirstFile(path.name, &fdata);

    if (handle == INVALID_HANDLE_VALUE) {
        pfs_errno = GetLastError();
        return -2;
    }

    do {
        Path *p = new Path;
        p->assign(fdata.cFileName);
        result.push_back(p);
    } while (FindNextFile(handle, &fdata));

    FindClose(handle);
#else
    DIR           *dir;
    struct dirent *ent;

    if (path.name == NULL)
        return -1;

    dir = opendir(path.name);

    if (dir == NULL) {
        pfs_errno = errno;
        return -2;
    }

    while ((ent = readdir(dir)) != NULL) {
        Path *p = new Path;
        p->assign(ent->d_name);
        result.push_back(p);
    }

    closedir(dir);
#endif

    return (int) result.size();
}

#define POM_ALL         (POM_READ | POM_WRITE           \
                         | POM_READWRITE | POM_TRUNC    \
                         | POM_CREAT | POM_OPEN_EXISTS)

file_handle_type
pfs_open(const Path& path, int flags, ...)
{
    file_handle_type handle;

    if (path.name == NULL)
        return PFS_INVALID_HANDLE;

    // mode checking
    if (flags == 0 || (flags & ~(POM_ALL)))
        return PFS_INVALID_HANDLE;

    if ((flags & POM_CREAT) && (flags & POM_OPEN_EXISTS))
        return PFS_INVALID_HANDLE;

#ifdef WIN32
    int rw;

    bool create_mode_empty = !(flags & (POM_CREAT | POM_OPEN_EXISTS));

    switch (flags & (POM_READ | POM_WRITE | POM_READWRITE)) {
    case POM_READ:
        rw = GENERIC_READ;
        if (create_mode_empty)
            flags |= POM_OPEN_EXISTS;
        break;

    case POM_WRITE:
        rw = GENERIC_WRITE;
        if (create_mode_empty)
            flags |= POM_CREAT;
        break;

    case POM_READWRITE:
        rw = GENERIC_WRITE | GENERIC_READ;
        if (create_mode_empty)
            flags |= POM_OPEN_EXISTS;
        break;
    default:
        return PFS_INVALID_HANDLE;
        break;
    }

    if (flags & POM_CREAT) {
        handle = CreateFile(path.name, rw,
                            FILE_SHARE_READ | FILE_SHARE_WRITE,
                            NULL,
                            OPEN_EXISTING,
                            FILE_ATTRIBUTE_NORMAL, NULL);

        if (!PFS_VALID_HANDLE_P(handle)) {
            handle = CreateFile(path.name, rw,
                                FILE_SHARE_READ | FILE_SHARE_WRITE,
                                NULL,
                                CREATE_NEW,
                                FILE_ATTRIBUTE_NORMAL, NULL);
        }
    }
    // open existing
    else {
        handle = CreateFile(path.name, rw,
                            FILE_SHARE_READ,
                            NULL,
                            OPEN_EXISTING,
                            FILE_ATTRIBUTE_NORMAL, NULL);
    }

    if (PFS_VALID_HANDLE_P(handle)) {
        if ((flags & (POM_WRITE | POM_READWRITE)) && (flags & POM_TRUNC)) {
            SetFilePointer(handle, 0, 0, FILE_BEGIN);

            if (!SetEndOfFile(handle)) {
                CloseHandle(handle);
                handle = PFS_INVALID_HANDLE;
            }
        }
    }
#else
    int native_falgs;

    switch (flags & (POM_READ | POM_WRITE | POM_READWRITE)) {
    case POM_READ:
        native_falgs = O_RDONLY;
        break;

    case POM_WRITE:
        native_falgs = O_WRONLY;
        break;

    case POM_READWRITE:
        native_falgs = O_RDWR;
        break;
    default:
        return PFS_INVALID_HANDLE;
        break;
    }

    int mode = 0;

    if (flags & POM_TRUNC)
        native_falgs |= O_TRUNC;

    if (flags & POM_CREAT) {
        native_falgs |= O_CREAT;

        va_list va;
        va_start(va, flags);
        mode = va_arg(va, int);
        va_end(va);
    }

    handle = open(path.name, native_falgs, mode);

#endif

    return handle;
}

pfs_int64
pfs_read(file_handle_type handle, void *ptr, pfs_int64 len)
{
    pfs_int64 rbytes;

    if (!PFS_VALID_HANDLE_P(handle) || len <= 0 || ptr == NULL)
        return 0;

    rbytes = 0;

#ifdef WIN32
    DWORD r;

    if (ReadFile(handle, ptr, (DWORD) len,
                 &r, NULL))
        rbytes = (pfs_int64) r;
#else
    ssize_t      r;

    while (rbytes < len) {
        r = read(handle, ((char *) ptr) + (size_t) rbytes,
                 (size_t) (len - rbytes));

        if (r > 0)
            rbytes += (pfs_int64) r;
        else if (r == 0)
            break;
        else {
            if (r != EINTR)
                break;
        }
    }
#endif

    return rbytes;
}

pfs_int64
pfs_write(file_handle_type handle, const void *ptr, pfs_int64 len)
{
    pfs_int64 wbytes;

    if (!PFS_VALID_HANDLE_P(handle) || len <= 0 || ptr == NULL)
        return 0;

    wbytes = 0;

#ifdef WIN32
    DWORD w;

    if (WriteFile(handle, ptr,
                  (DWORD) len,
                  &w, NULL))
        wbytes = (pfs_int64) w;
#else
    ssize_t r;
    while (wbytes < len) {
        r = write(handle, ((char *) ptr) + (size_t) wbytes,
                  (size_t) (len - wbytes));
        if (r > 0)
            wbytes += (pfs_int64) r;
        else {
            if (errno != EINTR)
                break;
        }
    }
#endif

    return wbytes;
}

pfs_int64
pfs_seek(file_handle_type handle, pfs_int64 pos, int method)
{
    int native_method;

    if (!PFS_VALID_HANDLE_P(handle) || method < PSM_BEGIN
        || method > PSM_END)
        return (pfs_int64) -1;

#ifdef WIN32
    switch (method) {
    case PSM_BEGIN:
        native_method = FILE_BEGIN;
        break;

    case PSM_CURRENT:
        native_method = FILE_CURRENT;
        break;

    default:
        native_method = FILE_END;
        break;
    }

    LONG low  = (LONG) pos;
    LONG high = (LONG) ((pfs_uint64)pos >> 32);

    DWORD r = SetFilePointer(handle, low, &high, native_method);

    if (r == INVALID_SET_FILE_POINTER)
        return (pfs_uint64) -1;

    return (((pfs_uint64)high << 32)
            | (pfs_int64)low);
#else
    switch (method) {
    case PSM_BEGIN:
        native_method = SEEK_SET;
        break;

    case PSM_CURRENT:
        native_method = SEEK_CUR;
        break;

    default:
        native_method = SEEK_END;
        break;
    }

    return (pfs_int64) lseek(handle, (off_t) pos,
                             native_method);
#endif
}

pfs_int64
pfs_get_file_size(file_handle_type handle)
{
    if (!PFS_VALID_HANDLE_P(handle))
        return (pfs_int64) -1;

#ifdef WIN32
    DWORD high;
    DWORD low;

    low = GetFileSize(handle, &high);

    if (low == INVALID_FILE_SIZE)
        return (pfs_int64) -1;

    return (((pfs_uint64)high << 32)
            | (pfs_int64)low);
#else
    struct stat sb;

    if (fstat(handle, &sb) < 0)
        return (pfs_int64) -1;

    return (pfs_int64) sb.st_size;
#endif
}

pfs_int64
pfs_get_file_size(const Path& path)
{
    if (path.name == NULL)
        return (pfs_int64) -1;

#ifdef WIN32
    WIN32_FILE_ATTRIBUTE_DATA fa;
    if (GetFileAttributesEx(path.name,
                            GetFileExInfoStandard,
                            &fa))
        return (pfs_int64) ((pfs_uint64)fa.nFileSizeLow
                               | ((pfs_uint64)fa.nFileSizeHigh << 32));

    return (pfs_int64) -1;
#else
    struct stat sb;

    if (stat(path.name, &sb) < 0)
        return (pfs_int64) -1;

    return (pfs_int64) sb.st_size;
#endif
}

bool
pfs_set_file_size(file_handle_type handle, pfs_int64 len)
{
    if (!PFS_VALID_HANDLE_P(handle) || len < 0)
        return false;

#ifdef WIN32
    LONG high = (pfs_uint64) len >> 32;
    LONG low  = (LONG) len;

    SetFilePointer(handle, low, &high, FILE_BEGIN);

    return ((SetEndOfFile(handle)) ? true : false);
#else
    return ftruncate(handle, (off_t) len) == 0;
#endif
}

int
pfs_close(file_handle_type handle)
{
    if (!PFS_VALID_HANDLE_P(handle))
        return -1;

#ifdef WIN32
    return CloseHandle(handle) ? 0 : -1;
#else
    return close(handle);
#endif
}

int
pfs_unlink(const Path& path)
{
#ifdef WIN32
    return (DeleteFile(path.name) ? 0 : -1);
#else
    return unlink(path.name);
#endif
}

bool
pfs_lock_entire(file_handle_type handle)
{
    if (!PFS_VALID_HANDLE_P(handle))
        return false;

#ifdef WIN32
    pfs_int64 size = pfs_get_file_size(handle);

    if (size < 0)
        return false;

    OVERLAPPED o;

    memset(&o, 0, sizeof (OVERLAPPED));

    if (LockFileEx(handle, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,
                   0, (DWORD) size,
                   (DWORD) ((pfs_uint64) size >> 32), &o))
        return true;

    return false;
#else
    struct flock lock;

    lock.l_whence = SEEK_SET;
    lock.l_start  = 0;
    lock.l_len    = 0;
    lock.l_type   = F_WRLCK;

    return ((fcntl(handle, F_SETLK, &lock)) == 0);
#endif
}

bool
pfs_unlock_entire(file_handle_type handle)
{
    if (!PFS_VALID_HANDLE_P(handle))
        return false;

#ifdef WIN32
    pfs_int64 size = pfs_get_file_size(handle);

    if (size < 0)
        return false;

    return (UnlockFile(handle, 0, 0,
                       (DWORD) size,
                       (DWORD) ((pfs_uint64) size >> 32))
            ? true
            : false);
#else
    struct flock lock;

    lock.l_whence = SEEK_SET;
    lock.l_start  = 0;
    lock.l_len    = 0;
    lock.l_type   = F_UNLCK;

    return ((fcntl(handle, F_SETLK, &lock)) == 0);
#endif
}

PfsFileType
pfs_get_file_type(const Path& path)
{
    if (path.name == NULL)
        return PFT_NOT_EXISTS;

#ifdef WIN32
    DWORD r = GetFileAttributes(path.name);

    if (r == INVALID_FILE_ATTRIBUTES)
        return PFT_NOT_EXISTS;

    if (r & FILE_ATTRIBUTE_NORMAL)
        return PFT_REG;
    else if (r & FILE_ATTRIBUTE_DIRECTORY)
        return PFT_DIR;

    return PFT_OTH;
#else
    struct stat sb;

    if (stat(path.name, &sb) < 0)
        return PFT_NOT_EXISTS;

    if (S_ISREG(sb.st_mode))
        return PFT_REG;
    else if (S_ISDIR(sb.st_mode))
        return PFT_DIR;

    return PFT_OTH;
#endif
}

DyBuf *
pfs_load_file(const Path& path)
{
    file_handle_type handle =
        pfs_open(path, POM_READ | POM_OPEN_EXISTS);

    if (!PFS_VALID_HANDLE_P(handle))
        return NULL;

    int size = (int) pfs_get_file_size(handle);

    if (size <= 0) {
        pfs_close(handle);
        return NULL;
    }

    DyBuf *dybuf = new DyBuf(size);

    char *buf = dybuf->get_buf();
    if (pfs_read(handle, buf, size) != size) {
        pfs_close(handle);
        delete dybuf;
        return NULL;
    }

    dybuf->buf_right += size;
    buf[size] = 0;

    pfs_close(handle);

    return dybuf;
}
